package com.hedl.practice.diyJava.encryptionandsecurity.symmetric_encryption_algorithm.test;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class Main_1 {
    /**
     * ECB模式是最简单的AES加密模式，它只需要一个固定长度的密钥，固定的明文会生成固定的密文，
     * 这种一对一的加密方式会导致安全性降低，更好的方式是通过CBC模式，它需要一个随机数作为IV参数，
     * 这样对于同一份明文，每次生成的密文都不同：
     */
    private static byte[] join(byte[] bs1,byte[] bs2){  //bs1=-4, -103, 59, 98, 126, 114, -75, 85, 86, -2, -100, 30, 64, 106, 1, 68
                                                        //bs2=119, -3, 88, -77, 84, -85, 67, -48, -94, -88, -84, 7, 54, 4, 77, 124
        byte[] r = new byte[bs1.length+bs2.length];
        //arraycopy(源数组,源数组中的起始位置,目标数组,目标数据中的起始位置,要复制的数组元素的数量)
        System.arraycopy(bs1,0,r,0,bs1.length);
        System.arraycopy(bs2,0,r,bs1.length,bs2.length);
        return r;
    }

    //解密
    private static byte[] decrypt(byte[] key,byte[] input) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        //把input分割成iv和密文
        //input=[-4, -103, 59, 98, 126, 114, -75, 85, 86, -2, -100, 30, 64, 106, 1, 68, 119, -3, 88, -77, 84, -85, 67, -48, -94, -88, -84, 7, 54, 4, 77, 124]
        byte[] iv = new byte[16];
        byte[] data = new byte[input.length-16];


        System.arraycopy(input,0,iv,0,16);
        //iv=-4, -103, 59, 98, 126, 114, -75, 85, 86, -2, -100, 30, 64, 106, 1, 68,
        System.arraycopy(input,16,data,0,data.length);
        //data=119, -3, 88, -77, 84, -85, 67, -48, -94, -88, -84, 7, 54, 4, 77, 124]

        //解密
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");  //keySpec=[49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 97, 98, 99, 100, 101, 102, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 97, 98, 99, 100, 101, 102]
        IvParameterSpec ivps = new IvParameterSpec(iv); //iv=-4, -103, 59, 98, 126, 114, -75, 85, 86, -2, -100, 30, 64, 106, 1, 68,
        cipher.init(Cipher.DECRYPT_MODE,keySpec,ivps);  //init(解密,keySpec,ivps)
        return cipher.doFinal(data);        //data=119, -3, 88, -77, 84, -85, 67, -48, -94, -88, -84, 7, 54, 4, 77, 124]
    }

    //加密
    private static byte[] encrypt(byte[] key,byte[] input) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        //提供用于加密和解密的加密密码的功能
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        //SecretKeySpec(密钥,算法)   从给定的字节数组构造一个密钥。
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        //CBC模式需要生成一个16 bytes的initializtion vector:
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] iv = sr.generateSeed(16);
        IvParameterSpec ivps = new IvParameterSpec(iv);     //使用 iv的字节作为IV创建IvParameterSpec对象。
        cipher.init(Cipher.ENCRYPT_MODE,keySpec,ivps);      //init(-此cipher的操作模式,加密密钥,算法参数)
        byte[] data = cipher.doFinal(input);        //在单部分操作中加密或解密数据
        //IV不需要保密，把iv和密文一起返回
        return join(iv,data);
    }

    public static void main(String[] args) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        //原文
        String message = "Hello,World";
        System.out.println("Message："+message);
        //256位密钥=32 bytes Key
        byte[] ket = "1234567890abcdef1234567890abcdef".getBytes(StandardCharsets.UTF_8);

        //加密
        byte[] data = message.getBytes(StandardCharsets.UTF_8);
        byte[] encrypted = encrypt(ket,data);
        System.out.println("Encrypted："+ Base64.getEncoder().encodeToString(encrypted));    //[-4, -103, 59, 98, 126, 114, -75, 85, 86, -2, -100, 30, 64, 106, 1, 68, 119, -3, 88, -77, 84, -85, 67, -48, -94, -88, -84, 7, 54, 4, 77, 124]

        //解密
        byte[] decrypted = decrypt(ket,encrypted);      //[72, 101, 108, 108, 111, 44, 87, 111, 114, 108, 100]
        System.out.println("Decrypted："+new String(decrypted,StandardCharsets.UTF_8));
    }

    /**
     * 在CBC模式下，需要一个随机生成的16字节IV参数，必须使用SecureRandom生成。因为多了一个IvParameterSpec实例，因此，初始化方法需要调用Cipher的一个重载方法并传入IvParameterSpec。
     *
     * 小结：
     *      对称加密算法使用同一个密钥进行加密和解密，常用算法有DES、AES和IDEA等；
     *      密钥长度由算法设计决定，AES的密钥长度是128/192/256位；
     *      使用对称加密算法需要指定算法名称、工作模式和填充模式。
     */
}
